agora inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH v3 1/7] Track retired system procedures in pg_retired.dat
542+ messages / 2 participants
[nested] [flat]

* [PATCH v3 1/7] Track retired system procedures in pg_retired.dat
@ 2026-01-29 23:47 Mark Wong <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Mark Wong @ 2026-01-29 23:47 UTC (permalink / raw)

System procedures may be removed from time to time.  Keep track of
previously used OIDs to prevent potential conflicts from reusing OIDs
after an upgrade.
---
 src/include/catalog/pg_retired.dat   | 20 ++++++++++++++++++++
 src/include/catalog/renumber_oids.pl |  9 +++++++++
 src/include/catalog/unused_oids      |  7 +++++++
 3 files changed, 36 insertions(+)
 create mode 100644 src/include/catalog/pg_retired.dat

diff --git a/src/include/catalog/pg_retired.dat b/src/include/catalog/pg_retired.dat
new file mode 100644
index 00000000000..413b68ad1cd
--- /dev/null
+++ b/src/include/catalog/pg_retired.dat
@@ -0,0 +1,20 @@
+#----------------------------------------------------------------------
+#
+# pg_retired.dat
+#    Entries that have been removed from pg_proc.dat with OIDs that are
+#    are not to be reused.
+#
+# Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/include/catalog/pg_retired.dat
+#
+#----------------------------------------------------------------------
+
+[
+
+# What's primarily important here is knowing which OIDs are not to be reused.
+# At the same time, it may be good to be reminded what procedure was associated
+# with it.
+
+]
diff --git a/src/include/catalog/renumber_oids.pl b/src/include/catalog/renumber_oids.pl
index f6e25d0f1e1..7489be02a50 100755
--- a/src/include/catalog/renumber_oids.pl
+++ b/src/include/catalog/renumber_oids.pl
@@ -68,6 +68,15 @@ my $oids = Catalog::FindAllOidsFromHeaders(@header_files);
 my %oidhash;
 @oidhash{@$oids} = undef;
 
+# Also include OIDs listed in pg_retired.dat to prevent new OIDs to be mapped
+# to OIDs that have been retired.
+my $retired_data = Catalog::ParseData('pg_retired.dat', undef, 0);
+foreach my $row (@$retired_data)
+{
+	$oidhash{ $row->{oid} } = undef
+	  if ref $row eq 'HASH' && defined $row->{oid};
+}
+
 # Select new OIDs for existing OIDs in the mapped range.
 # We do this first so that we preserve the ordering of the mapped OIDs
 # (for reproducibility's sake), and so that if we fail due to running out
diff --git a/src/include/catalog/unused_oids b/src/include/catalog/unused_oids
index 147fba5e209..aeb485d9b5e 100755
--- a/src/include/catalog/unused_oids
+++ b/src/include/catalog/unused_oids
@@ -32,6 +32,13 @@ my @input_files = glob("pg_*.h");
 
 my $oids = Catalog::FindAllOidsFromHeaders(@input_files);
 
+# Also exclude OIDs listed in pg_retired.dat
+my $retired_data = Catalog::ParseData('pg_retired.dat', undef, 0);
+foreach my $row (@$retired_data)
+{
+	push @{$oids}, $row->{oid} if ref $row eq 'HASH' && defined $row->{oid};
+}
+
 # Also push FirstGenbkiObjectId to serve as a terminator for the last gap.
 my $FirstGenbkiObjectId =
   Catalog::FindDefinedSymbol('access/transam.h', '..', 'FirstGenbkiObjectId');
-- 
2.43.0


--zd8Z8rlPOcTi77n/
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
	filename="v3-0002-Handle-pg_get_ruledef-default-args-in-system_func.patch"



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 542+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



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


end of thread, other threads:[~2026-07-15 08:37 UTC | newest]

Thread overview: 542+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-01-29 23:47 [PATCH v3 1/7] Track retired system procedures in pg_retired.dat Mark Wong <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>

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